/**
 * Represents a canvas on which BPMN 2.0 constructs can be drawn.
 *
 * Some of the icons used are licenced under a Creative Commons Attribution 2.5
 * License, see http://www.famfamfam.com/lab/icons/silk/
 *
 * @see ProcessDiagramGenerator
 * @author (Java) Joram Barrez
 * @author (Javascript) Dmitry Farafonov
 */

//Color.Cornsilk

var ARROW_HEAD_SIMPLE = 'simple';
var ARROW_HEAD_EMPTY = 'empty';
var ARROW_HEAD_FILL = 'FILL';
var MULTILINE_VERTICAL_ALIGN_TOP = 'top';
var MULTILINE_VERTICAL_ALIGN_MIDDLE = 'middle';
var MULTILINE_VERTICAL_ALIGN_BOTTOM = 'bottom';
var MULTILINE_HORIZONTAL_ALIGN_LEFT = 'start';
var MULTILINE_HORIZONTAL_ALIGN_MIDDLE = 'middle';
var MULTILINE_HORIZONTAL_ALIGN_RIGHT = 'end';

// Predefined sized
var TEXT_PADDING = 3;
var ARROW_WIDTH = 4;
var CONDITIONAL_INDICATOR_WIDTH = 16;
var MARKER_WIDTH = 12;
var ANNOTATION_TEXT_PADDING = 7;

// Colors
var TASK_COLOR = Color.OldLace; // original: Color.get(255, 255, 204);
var TASK_STROKE_COLOR = Color.black; /*Color.SlateGrey; */
//var EXPANDED_SUBPROCESS_ATTRS = Color.black; /*Color.SlateGrey; */
var BOUNDARY_EVENT_COLOR = Color.white;
var CONDITIONAL_INDICATOR_COLOR = Color.get(255, 255, 255);
var HIGHLIGHT_COLOR = Color.Firebrick1;
//var SEQUENCEFLOW_COLOR = Color.DimGrey;
var SEQUENCEFLOW_COLOR = Color.black;

var CATCHING_EVENT_COLOR = Color.black; /* Color.SlateGrey; */
var START_EVENT_COLOR = Color.get(251, 251, 251);
var START_EVENT_STROKE_COLOR = Color.black; /* Color.SlateGrey; */
var END_EVENT_COLOR = Color.get(251, 251, 251);
//var END_EVENT_STROKE_COLOR = Color.black;
var NONE_END_EVENT_COLOR = Color.Firebrick4;
var NONE_END_EVENT_STROKE_COLOR = Color.Firebrick4;
var ERROR_END_EVENT_COLOR = Color.Firebrick;
var ERROR_END_EVENT_STROKE_COLOR = Color.Firebrick;
//var LABEL_COLOR = Color.get(112, 146, 190);
var LABEL_COLOR = Color.get(72, 106, 150);

// Fonts
var NORMAL_FONT = { font: '10px Arial', opacity: 1, fill: Color.black };
var LABEL_FONT = { font: '11px Arial', 'font-style': 'italic', opacity: 1, fill: LABEL_COLOR };
var LABEL_FONT_SMOOTH = {
  font: '10px Arial',
  'font-style': 'italic',
  opacity: 1,
  fill: LABEL_COLOR,
  stroke: LABEL_COLOR,
  'stroke-width': 0.4,
};
var TASK_FONT = { font: '11px Arial', opacity: 1, fill: Color.black };
var TASK_FONT_SMOOTH = { font: '11px Arial', opacity: 1, fill: Color.black, stroke: LABEL_COLOR, 'stroke-width': 0.4 };
var POOL_LANE_FONT = { font: '11px Arial', opacity: 1, fill: Color.black };
var EXPANDED_SUBPROCESS_FONT = { font: '11px Arial', opacity: 1, fill: Color.black };

// Strokes
var NORMAL_STROKE = 1;
var SEQUENCEFLOW_STROKE = 1.5;
var SEQUENCEFLOW_HIGHLIGHT_STROKE = 2;
var THICK_TASK_BORDER_STROKE = 2.5;
var GATEWAY_TYPE_STROKE = 3.2;
var END_EVENT_STROKE = NORMAL_STROKE + 2;
var MULTI_INSTANCE_STROKE = 1.3;
var EVENT_SUBPROCESS_ATTRS = { stroke: Color.black, 'stroke-width': NORMAL_STROKE, 'stroke-dasharray': '. ' };
//var EXPANDED_SUBPROCESS_ATTRS = {"stroke": Color.black, "stroke-width": NORMAL_STROKE, "fill": Color.FloralWhite};
var EXPANDED_SUBPROCESS_ATTRS = { stroke: Color.black, 'stroke-width': NORMAL_STROKE, fill: Color.WhiteSmoke };
var NON_INTERRUPTING_EVENT_STROKE = '- ';

var TASK_CORNER_ROUND = 10;
var EXPANDED_SUBPROCESS_CORNER_ROUND = 10;

// icons
var ICON_SIZE = 16;
var ICON_PADDING = 4;
var USERTASK_IMAGE = 'images/deployer/user.png';
var SCRIPTTASK_IMAGE = 'images/deployer/script.png';
var SERVICETASK_IMAGE = 'images/deployer/service.png';
var RECEIVETASK_IMAGE = 'images/deployer/receive.png';
var SENDTASK_IMAGE = 'images/deployer/send.png';
var MANUALTASK_IMAGE = 'images/deployer/manual.png';
var BUSINESS_RULE_TASK_IMAGE = 'images/deployer/business_rule.png';
var TIMER_IMAGE = 'images/deployer/timer.png';
var MESSAGE_CATCH_IMAGE = 'images/deployer/message_catch.png';
var MESSAGE_THROW_IMAGE = 'images/deployer/message_throw.png';
var ERROR_THROW_IMAGE = 'images/deployer/error_throw.png';
var ERROR_CATCH_IMAGE = 'images/deployer/error_catch.png';
var SIGNAL_CATCH_IMAGE = 'images/deployer/signal_catch.png';
var SIGNAL_THROW_IMAGE = 'images/deployer/signal_throw.png';
var MULTIPLE_CATCH_IMAGE = 'images/deployer/multiple_catch.png';

var ObjectType = {
  ELLIPSE: 'ellipse',
  FLOW: 'flow',
  RECT: 'rect',
  RHOMBUS: 'rhombus',
};

function OBJ(type) {
  this.c = null;
  this.type = type;
  this.nestedElements = [];
}
OBJ.prototype = {};

var CONNECTION_TYPE = {
  SEQUENCE_FLOW: 'sequence_flow',
  MESSAGE_FLOW: 'message_flow',
  ASSOCIATION: 'association',
};

var ProcessDiagramCanvas = function () {};
ProcessDiagramCanvas.prototype = {
  // var DefaultProcessDiagramCanvas = {
  canvasHolder: 'holder',
  canvasWidth: 0,
  canvasHeight: 0,
  paint: Color.black,
  strokeWidth: 0,
  font: null,
  fontSmoothing: null,

  g: null,
  ninjaPaper: null,

  objects: [],

  processDefinitionId: null,
  activity: null,

  frame: null,

  debug: false,

  /**
   * Creates an empty canvas with given width and height.
   */
  init: function (width, height, processDefinitionId) {
    this.canvasWidth = width;
    this.canvasHeight = height;

    // TODO: name it as 'canvasName'
    if (!processDefinitionId) processDefinitionId = 'holder';

    this.processDefinitionId = processDefinitionId;
    this.canvasHolder = this.processDefinitionId;

    var h = document.getElementById(this.canvasHolder);
    if (!h) return;

    h.style.width = this.canvasWidth;
    h.style.height = this.canvasHeight;

    this.g = Raphael(this.canvasHolder);
    this.g.clear();

    //this.setPaint(Color.DimGrey);
    this.setPaint(Color.black);
    //this.setPaint(Color.white);
    this.setStroke(NORMAL_STROKE);

    //this.setFont("Arial", 11);
    this.setFont(NORMAL_FONT);
    //this.font = this.g.getFont("Arial");

    this.fontSmoothing = true;

    // ninja!
    var RaphaelOriginal = Raphael;
    this.ninjaPaper = (function (local_raphael) {
      var paper = local_raphael(1, 1, 1, 1, processDefinitionId);
      return paper;
    })(Raphael.ninja());
    Raphael = RaphaelOriginal;
  },
  setPaint: function (color) {
    this.paint = color;
  },
  getPaint: function () {
    return this.paint;
  },
  setStroke: function (strokeWidth) {
    this.strokeWidth = strokeWidth;
  },
  getStroke: function () {
    return this.strokeWidth;
  },
  /*
	setFont: function(family, weight, style, stretch){
		this.font = this.g.getFont(family, weight);
	},
	*/
  setFont: function (font) {
    this.font = font;
  },
  getFont: function () {
    return this.font;
  },
  drawShaddow: function (object) {
    var border = object.clone();
    border.attr({ 'stroke-width': this.strokeWidth + 6, stroke: Color.white, fill: Color.white, opacity: 1, 'stroke-dasharray': null });
    //border.toBack();
    object.toFront();

    return border;
  },

  setConextObject: function (obj) {
    this.contextObject = obj;
  },
  getConextObject: function () {
    return this.contextObject;
  },
  setContextToElement: function (object) {
    var contextObject = this.getConextObject();
    object.id = contextObject.id;
    object.data('contextObject', contextObject);
  },
  onClick: function (event, instance, element) {
    var overlay = element;
    var set = overlay.data('set');
    var contextObject = overlay.data('contextObject');
    //console.log("["+contextObject.getProperty("type")+"], activityId: " + contextObject.getId());
    if (ProcessDiagramGenerator.options && ProcessDiagramGenerator.options.on && ProcessDiagramGenerator.options.on.click) {
      var args = [instance, element, contextObject];
      ProcessDiagramGenerator.options.on.click.apply(event, args);
    }
  },
  onRightClick: function (event, instance, element) {
    var overlay = element;
    var set = overlay.data('set');
    var contextObject = overlay.data('contextObject');
    //console.log("[%s], activityId: %s (RIGHTCLICK)", contextObject.getProperty("type"), contextObject.getId());

    if (ProcessDiagramGenerator.options && ProcessDiagramGenerator.options.on && ProcessDiagramGenerator.options.on.rightClick) {
      var args = [instance, element, contextObject];
      ProcessDiagramGenerator.options.on.rightClick.apply(event, args);
    }
  },
  onHoverIn: function (event, instance, element) {
    var overlay = element;
    var set = overlay.data('set');
    var contextObject = overlay.data('contextObject');

    var border = instance.g.getById(contextObject.id + '_border');
    border.attr('opacity', 0.3);

    // provide callback
    if (ProcessDiagramGenerator.options && ProcessDiagramGenerator.options.on && ProcessDiagramGenerator.options.on.over) {
      var args = [instance, element, contextObject];
      ProcessDiagramGenerator.options.on.over.apply(event, args);
    }
  },
  onHoverOut: function (event, instance, element) {
    var overlay = element;
    var set = overlay.data('set');
    var contextObject = overlay.data('contextObject');

    var border = instance.g.getById(contextObject.id + '_border');
    border.attr('opacity', 0.0);
    // provide callback
    if (ProcessDiagramGenerator.options && ProcessDiagramGenerator.options.on && ProcessDiagramGenerator.options.on.out) {
      var args = [instance, element, contextObject];
      ProcessDiagramGenerator.options.on.out.apply(event, args);
    }
  },
  addHandlers: function (set, x, y, width, height, type) {
    var contextObject = this.getConextObject();

    var cx = x + width / 2,
      cy = y + height / 2;
    if (type == 'event') {
      var border = this.g.ellipse(cx, cy, width / 2 + 4, height / 2 + 4);
      var overlay = this.g.ellipse(cx, cy, width / 2, height / 2);
    } else if (type == 'gateway') {
      // rhombus
      var border = this.g.path(
        'M' +
          (x - 4) +
          ' ' +
          (y + height / 2) +
          'L' +
          (x + width / 2) +
          ' ' +
          (y + height + 4) +
          'L' +
          (x + width + 4) +
          ' ' +
          (y + height / 2) +
          'L' +
          (x + width / 2) +
          ' ' +
          (y - 4) +
          'z'
      );
      var overlay = this.g.path(
        'M' +
          x +
          ' ' +
          (y + height / 2) +
          'L' +
          (x + width / 2) +
          ' ' +
          (y + height) +
          'L' +
          (x + width) +
          ' ' +
          (y + height / 2) +
          'L' +
          (x + width / 2) +
          ' ' +
          y +
          'z'
      );
    } else if (type == 'task') {
      var border = this.g.rect(x - 4, y - 4, width + 9, height + 9, TASK_CORNER_ROUND + 4);
      var overlay = this.g.rect(x, y, width, height, TASK_CORNER_ROUND);
    }

    border.attr({ stroke: Color.get(132, 112, 255) /*Color.Tan1*/, 'stroke-width': 4, opacity: 0.0 });
    border.id = contextObject.id + '_border';

    set.push(border);

    overlay.attr({ stroke: Color.Orange, 'stroke-width': 3, fill: Color.get(0, 0, 0), opacity: 0.0, cursor: 'hand' });
    overlay.data('set', set);
    overlay.id = contextObject.id;
    overlay.data('contextObject', contextObject);

    var instance = this;
    overlay.mousedown(function (event) {
      if (event.button == 2) instance.onRightClick(event, instance, this);
    });
    overlay.click(function (event) {
      instance.onClick(event, instance, this);
    });
    overlay.hover(
      function (event) {
        instance.onHoverIn(event, instance, this);
      },
      function (event) {
        instance.onHoverOut(event, instance, this);
      }
    );
  },

  /*
   * Start Events:
   *
   *	drawNoneStartEvent
   *	drawTimerStartEvent
   *	drawMessageStartEvent
   *	drawErrorStartEvent
   *	drawSignalStartEvent
   *	_drawStartEventImage
   *	_drawStartEvent
   */

  drawNoneStartEvent: function (x, y, width, height) {
    this.g.setStart();

    var isInterrupting = undefined;
    this._drawStartEvent(x, y, width, height, isInterrupting, null);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawTimerStartEvent: function (x, y, width, height, isInterrupting, name) {
    this.g.setStart();

    this._drawStartEvent(x, y, width, height, isInterrupting, null);

    var cx = x + width / 2 - this.getStroke() / 4;
    var cy = y + height / 2 - this.getStroke() / 4;

    var w = width * 0.9; // - this.getStroke()*2;
    var h = height * 0.9; // - this.getStroke()*2;

    this._drawClock(cx, cy, w, h);

    if (this.gebug) var center = this.g.ellipse(cx, cy, 3, 3).attr({ stroke: 'none', fill: Color.green });

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawMessageStartEvent: function (x, y, width, height, isInterrupting, name) {
    this.g.setStart();

    this._drawStartEvent(x, y, width, height, isInterrupting, null);

    this._drawStartEventImage(x, y, width, height, MESSAGE_CATCH_IMAGE);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawErrorStartEvent: function (x, y, width, height, name) {
    this.g.setStart();
    var isInterrupting = undefined;
    this._drawStartEvent(x, y, width, height, isInterrupting);

    this._drawStartEventImage(x, y, width, height, ERROR_CATCH_IMAGE);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawSignalStartEvent: function (x, y, width, height, isInterrupting, name) {
    this.g.setStart();
    this._drawStartEvent(x, y, width, height, isInterrupting, null);

    this._drawStartEventImage(x, y, width, height, SIGNAL_CATCH_IMAGE);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawMultipleStartEvent: function (x, y, width, height, isInterrupting, name) {
    this.g.setStart();

    this._drawStartEvent(x, y, width, height, isInterrupting, null);

    var cx = x + width / 2 - this.getStroke() / 4;
    var cy = y + height / 2 - this.getStroke() / 4;

    var w = width * 1;
    var h = height * 1;

    this._drawPentagon(cx, cy, w, h);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  _drawStartEventImage: function (x, y, width, height, image) {
    var cx = x + width / 2 - this.getStroke() / 2;
    var cy = y + height / 2 - this.getStroke() / 2;

    var w = width * 0.65; // - this.getStroke()*2;
    var h = height * 0.65; // - this.getStroke()*2;

    var img = this.g.image(image, cx - w / 2, cy - h / 2, w, h);
  },
  _drawStartEvent: function (x, y, width, height, isInterrupting) {
    var originalPaint = this.getPaint();
    if (typeof START_EVENT_STROKE_COLOR != 'undefined') this.setPaint(START_EVENT_STROKE_COLOR);

    width -= this.strokeWidth / 2;
    height -= this.strokeWidth / 2;

    x = x + width / 2;
    y = y + height / 2;

    var circle = this.g.ellipse(x, y, width / 2, height / 2);

    circle.attr({
      'stroke-width': this.strokeWidth,
      stroke: this.paint,
      //"stroke": START_EVENT_STROKE_COLOR,
      fill: START_EVENT_COLOR,
    });

    // white shaddow
    this.drawShaddow(circle);

    if (isInterrupting != null && isInterrupting != undefined && !isInterrupting)
      circle.attr({ 'stroke-dasharray': NON_INTERRUPTING_EVENT_STROKE });

    this.setContextToElement(circle);

    this.setPaint(originalPaint);
  },

  /*
   * End Events:
   *
   *	drawNoneEndEvent
   *	drawErrorEndEvent
   *	drawMessageEndEvent
   *	drawSignalEndEvent
   *	drawMultipleEndEvent
   *  _drawEndEventImage
   *	_drawNoneEndEvent
   */

  drawNoneEndEvent: function (x, y, width, height) {
    this.g.setStart();

    this._drawNoneEndEvent(x, y, width, height, null, 'noneEndEvent');

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawErrorEndEvent: function (x, y, width, height) {
    this.g.setStart();
    var type = 'errorEndEvent';
    this._drawNoneEndEvent(x, y, width, height, null, type);

    this._drawEndEventImage(x, y, width, height, ERROR_THROW_IMAGE);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawMessageEndEvent: function (x, y, width, height, name) {
    this.g.setStart();
    var type = 'errorEndEvent';
    this._drawNoneEndEvent(x, y, width, height, null, type);

    this._drawEndEventImage(x, y, width, height, MESSAGE_THROW_IMAGE);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawSignalEndEvent: function (x, y, width, height, name) {
    this.g.setStart();
    var type = 'errorEndEvent';
    this._drawNoneEndEvent(x, y, width, height, null, type);

    this._drawEndEventImage(x, y, width, height, SIGNAL_THROW_IMAGE);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawMultipleEndEvent: function (x, y, width, height, name) {
    this.g.setStart();
    var type = 'errorEndEvent';
    this._drawNoneEndEvent(x, y, width, height, null, type);

    var cx = x + width / 2; // - this.getStroke();
    var cy = y + height / 2; // - this.getStroke();

    var w = width * 1;
    var h = height * 1;

    var filled = true;
    this._drawPentagon(cx, cy, w, h, filled);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawTerminateEndEvent: function (x, y, width, height) {
    this.g.setStart();
    var type = 'errorEndEvent';
    this._drawNoneEndEvent(x, y, width, height, null, type);

    var cx = x + width / 2; // - this.getStroke()/2;
    var cy = y + height / 2; // - this.getStroke()/2;

    var w = (width / 2) * 0.6;
    var h = (height / 2) * 0.6;

    var circle = this.g.ellipse(cx, cy, w, h).attr({ fill: Color.black });

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  _drawEndEventImage: function (x, y, width, height, image) {
    var cx = x + width / 2 - this.getStroke() / 2;
    var cy = y + height / 2 - this.getStroke() / 2;

    var w = width * 0.65;
    var h = height * 0.65;

    var img = this.g.image(image, cx - w / 2, cy - h / 2, w, h);
  },

  _drawNoneEndEvent: function (x, y, width, height, image, type) {
    var originalPaint = this.getPaint();
    if (typeof CATCHING_EVENT_COLOR != 'undefined') this.setPaint(CATCHING_EVENT_COLOR);

    var strokeColor = this.getPaint();
    var fillColor = this.getPaint();

    if (type == 'errorEndEvent') {
      strokeColor = ERROR_END_EVENT_STROKE_COLOR;
      fillColor = ERROR_END_EVENT_COLOR;
    } else if (type == 'noneEndEvent') {
      strokeColor = NONE_END_EVENT_STROKE_COLOR;
      fillColor = NONE_END_EVENT_COLOR;
    }

    // event circles
    else width -= this.strokeWidth / 2;
    height -= this.strokeWidth / 2;

    x = x + width / 2; // + this.strokeWidth/2;
    y = y + width / 2; // + this.strokeWidth/2;

    // outerCircle
    var outerCircle = this.g.ellipse(x, y, width / 2, height / 2);

    // white shaddow
    var shaddow = this.drawShaddow(outerCircle);

    outerCircle.attr({ 'stroke-width': this.strokeWidth, stroke: strokeColor, fill: fillColor });

    var innerCircleX = x;
    var innerCircleY = y;
    var innerCircleWidth = width / 2 - 2;
    var innerCircleHeight = height / 2 - 2;
    var innerCircle = this.g.ellipse(innerCircleX, innerCircleY, innerCircleWidth, innerCircleHeight);
    innerCircle.attr({ 'stroke-width': this.strokeWidth, stroke: strokeColor, fill: Color.white });

    // TODO: implement it
    //var originalPaint = this.getPaint();
    //this.g.setPaint(BOUNDARY_EVENT_COLOR);

    this.setPaint(originalPaint);
  },

  /*
   * Catching Events:
   *
   *	drawCatchingTimerEvent
   *	drawCatchingErrorEvent
   *	drawCatchingSignalEvent
   *  drawCatchingMessageEvent
   *	drawCatchingMultipleEvent
   *	_drawCatchingEventImage
   *	_drawCatchingEvent
   */

  drawCatchingTimerEvent: function (x, y, width, height, isInterrupting, name) {
    this.g.setStart();
    this._drawCatchingEvent(x, y, width, height, isInterrupting, null);

    var innerCircleWidth = width - 4;
    var innerCircleHeight = height - 4;

    var cx = x + width / 2 - this.getStroke() / 4;
    var cy = y + height / 2 - this.getStroke() / 4;

    var w = innerCircleWidth * 0.9; // - this.getStroke()*2;
    var h = innerCircleHeight * 0.9; // - this.getStroke()*2;

    this._drawClock(cx, cy, w, h);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawCatchingErrorEvent: function (x, y, width, height, isInterrupting, name) {
    this.g.setStart();
    this._drawCatchingEvent(x, y, width, height, isInterrupting, null);

    this._drawCatchingEventImage(x, y, width, height, ERROR_CATCH_IMAGE);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawCatchingSignalEvent: function (x, y, width, height, isInterrupting, name) {
    this.g.setStart();
    this._drawCatchingEvent(x, y, width, height, isInterrupting, null);

    this._drawCatchingEventImage(x, y, width, height, SIGNAL_CATCH_IMAGE);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawCatchingMessageEvent: function (x, y, width, height, isInterrupting, name) {
    this.g.setStart();
    this._drawCatchingEvent(x, y, width, height, isInterrupting, null);

    this._drawCatchingEventImage(x, y, width, height, MESSAGE_CATCH_IMAGE);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawCatchingMultipleEvent: function (x, y, width, height, isInterrupting, name) {
    this.g.setStart();
    this._drawCatchingEvent(x, y, width, height, isInterrupting, null);

    var cx = x + width / 2 - this.getStroke();
    var cy = y + height / 2 - this.getStroke();

    var w = width * 0.9;
    var h = height * 0.9;

    this._drawPentagon(cx, cy, w, h);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  _drawCatchingEventImage: function (x, y, width, height, image) {
    var innerCircleWidth = width - 4;
    var innerCircleHeight = height - 4;

    var cx = x + width / 2 - this.getStroke() / 2;
    var cy = y + height / 2 - this.getStroke() / 2;

    var w = innerCircleWidth * 0.6; // - this.getStroke()*2;
    var h = innerCircleHeight * 0.6; // - this.getStroke()*2;

    var img = this.g.image(image, cx - w / 2, cy - h / 2, w, h);
  },

  _drawCatchingEvent: function (x, y, width, height, isInterrupting, image) {
    var originalPaint = this.getPaint();
    if (typeof CATCHING_EVENT_COLOR != 'undefined') this.setPaint(CATCHING_EVENT_COLOR);

    // event circles
    width -= this.strokeWidth / 2;
    height -= this.strokeWidth / 2;

    x = x + width / 2; // + this.strokeWidth/2;
    y = y + width / 2; // + this.strokeWidth/2;

    // outerCircle
    var outerCircle = this.g.ellipse(x, y, width / 2, height / 2);

    // white shaddow
    var shaddow = this.drawShaddow(outerCircle);

    //console.log("isInterrupting: " + isInterrupting, "x:" , x, "y:",y);
    if (isInterrupting != null && isInterrupting != undefined && !isInterrupting)
      outerCircle.attr({ 'stroke-dasharray': NON_INTERRUPTING_EVENT_STROKE });

    outerCircle.attr({ 'stroke-width': this.strokeWidth, stroke: this.getPaint(), fill: BOUNDARY_EVENT_COLOR });

    var innerCircleX = x;
    var innerCircleY = y;
    var innerCircleRadiusX = width / 2 - 4;
    var innerCircleRadiusY = height / 2 - 4;
    var innerCircle = this.g.ellipse(innerCircleX, innerCircleY, innerCircleRadiusX, innerCircleRadiusY);
    innerCircle.attr({ 'stroke-width': this.strokeWidth, stroke: this.getPaint() });

    if (image) {
      var imageWidth = (imageHeight = innerCircleRadiusX * 1.2 + this.getStroke() * 2);
      var imageX = innerCircleX - imageWidth / 2 - this.strokeWidth / 2;
      var imageY = innerCircleY - imageWidth / 2 - this.strokeWidth / 2;
      var img = this.g.image(image, imageX, imageY, imageWidth, imageHeight);
    }

    this.setPaint(originalPaint);

    var set = this.g.set();
    set.push(outerCircle, innerCircle, shaddow);
    this.setContextToElement(outerCircle);

    // TODO: add shapes to set

    /*
		var st = this.g.set();
		st.push(
			this.g.ellipse(innerCircleX, innerCircleY, 2, 2),
			this.g.ellipse(imageX, imageY, 2, 2)
		);
		st.attr({fill: "red", "stroke-width":0});
		*/
  },

  /*
   * Catching Events:
   *
   *	drawThrowingNoneEvent
   *	drawThrowingSignalEvent
   *	drawThrowingMessageEvent
   *	drawThrowingMultipleEvent
   */

  drawThrowingNoneEvent: function (x, y, width, height, name) {
    this.g.setStart();
    this._drawCatchingEvent(x, y, width, height, null, null);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawThrowingSignalEvent: function (x, y, width, height, name) {
    this.g.setStart();
    this._drawCatchingEvent(x, y, width, height, null, null);

    this._drawCatchingEventImage(x, y, width, height, SIGNAL_THROW_IMAGE);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawThrowingMessageEvent: function (x, y, width, height, name) {
    this.g.setStart();
    this._drawCatchingEvent(x, y, width, height, null, null);

    this._drawCatchingEventImage(x, y, width, height, MESSAGE_THROW_IMAGE);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  drawThrowingMultipleEvent: function (x, y, width, height, name) {
    this.g.setStart();
    this._drawCatchingEvent(x, y, width, height, null, null);

    var cx = x + width / 2 - this.getStroke();
    var cy = y + height / 2 - this.getStroke();

    var w = width * 0.9;
    var h = height * 0.9;

    var filled = true;
    this._drawPentagon(cx, cy, w, h, filled);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'event');
  },

  /*
   * Draw flows:
   *
   *  _connectFlowToActivity
   *	_drawFlow
   *	_drawDefaultSequenceFlowIndicator
   *	drawSequenceflow
   *	drawMessageflow
   *	drawAssociation
   *	_drawCircleTail
   *	_drawArrowHead
   *	_drawConditionalSequenceFlowIndicator
   *	drawSequenceflowWithoutArrow
   */

  _connectFlowToActivity: function (sourceActivityId, destinationActivityId, waypoints) {
    var sourceActivity = this.g.getById(sourceActivityId);
    var destinationActivity = this.g.getById(destinationActivityId);
    if (sourceActivity == null || destinationActivity == null) {
      if (sourceActivity == null) console.error('source activity[' + sourceActivityId + '] not found');
      else console.error('destination activity[' + destinationActivityId + '] not found');
      return null;
    }
    var bbSourceActivity = sourceActivity.getBBox();
    var bbDestinationActivity = destinationActivity.getBBox();

    var path = [];
    var newWaypoints = [];
    for (var i = 0; i < waypoints.length; i++) {
      var pathType = '';
      if (i == 0) pathType = 'M';
      else pathType = 'L';

      path.push([pathType, waypoints[i].x, waypoints[i].y]);
      newWaypoints.push({ x: waypoints[i].x, y: waypoints[i].y });
    }

    var ninjaPathSourceActivity = this.ninjaPaper.path(sourceActivity.realPath);
    var ninjaPathDestinationActivity = this.ninjaPaper.path(destinationActivity.realPath);
    var ninjaBBSourceActivity = ninjaPathSourceActivity.getBBox();
    var ninjaBBDestinationActivity = ninjaPathDestinationActivity.getBBox();

    // set target of the flow to the center of the taskObject
    var newPath = path;
    var originalSource = { x: newPath[0][1], y: newPath[0][2] };
    var originalTarget = { x: newPath[newPath.length - 1][1], y: newPath[newPath.length - 1][2] };
    newPath[0][1] = ninjaBBSourceActivity.x + (ninjaBBSourceActivity.x2 - ninjaBBSourceActivity.x) / 2;
    newPath[0][2] = ninjaBBSourceActivity.y + (ninjaBBSourceActivity.y2 - ninjaBBSourceActivity.y) / 2;
    newPath[newPath.length - 1][1] = ninjaBBDestinationActivity.x + (ninjaBBDestinationActivity.x2 - ninjaBBDestinationActivity.x) / 2;
    newPath[newPath.length - 1][2] = ninjaBBDestinationActivity.y + (ninjaBBDestinationActivity.y2 - ninjaBBDestinationActivity.y) / 2;

    var ninjaPathFlowObject = this.ninjaPaper.path(newPath);
    var ninjaBBFlowObject = ninjaPathFlowObject.getBBox();

    var intersectionsSource = Raphael.pathIntersection(ninjaPathSourceActivity.realPath, ninjaPathFlowObject.realPath);
    var intersectionsDestination = Raphael.pathIntersection(ninjaPathDestinationActivity.realPath, ninjaPathFlowObject.realPath);
    var intersectionSource = intersectionsSource.pop();
    var intersectionDestination = intersectionsDestination.pop();

    if (intersectionSource != undefined) {
      if (this.gebug) {
        var diameter = 5;
        var dotOriginal = this.g
          .ellipse(originalSource.x, originalSource.y, diameter, diameter)
          .attr({ fill: Color.white, stroke: Color.Pink });
        var dot = this.g
          .ellipse(intersectionSource.x, intersectionSource.y, diameter, diameter)
          .attr({ fill: Color.white, stroke: Color.Green });
      }

      newWaypoints[0].x = intersectionSource.x;
      newWaypoints[0].y = intersectionSource.y;
    }
    if (intersectionDestination != undefined) {
      if (this.gebug) {
        var diameter = 5;
        var dotOriginal = this.g
          .ellipse(originalTarget.x, originalTarget.y, diameter, diameter)
          .attr({ fill: Color.white, stroke: Color.Red });
        var dot = this.g
          .ellipse(intersectionDestination.x, intersectionDestination.y, diameter, diameter)
          .attr({ fill: Color.white, stroke: Color.Blue });
      }

      newWaypoints[newWaypoints.length - 1].x = intersectionDestination.x;
      newWaypoints[newWaypoints.length - 1].y = intersectionDestination.y;
    }

    this.ninjaPaper.clear();
    return newWaypoints;
  },

  _drawFlow: function (waypoints, conditional, isDefault, highLighted, withArrowHead, connectionType) {
    var originalPaint = this.getPaint();
    var originalStroke = this.getStroke();

    this.setPaint(SEQUENCEFLOW_COLOR);
    this.setStroke(SEQUENCEFLOW_STROKE);

    if (highLighted) {
      this.setPaint(HIGHLIGHT_COLOR);
      this.setStroke(SEQUENCEFLOW_HIGHLIGHT_STROKE);
    }

    // TODO: generate polylineId or do something!!
    var uuid = Raphael.createUUID();

    var contextObject = this.getConextObject();
    var newWaypoints = waypoints;
    if (contextObject) {
      var newWaypoints = this._connectFlowToActivity(contextObject.sourceActivityId, contextObject.destinationActivityId, waypoints);

      if (!newWaypoints) {
        console.error("Error draw flow from '" + contextObject.sourceActivityId + "' to '" + contextObject.destinationActivityId + "' ");
        return;
      }
    }
    var polyline = new Polyline(uuid, newWaypoints, this.getStroke());
    //var polyline = new Polyline(waypoints, 3);

    polyline.element = this.g.path(polyline.path);
    polyline.element.attr('stroke-width', this.getStroke());
    polyline.element.attr('stroke', this.getPaint());

    if (contextObject) {
      polyline.element.id = contextObject.id;
      polyline.element.data('contextObject', contextObject);
    } else {
      polyline.element.id = uuid;
    }

    /*
		polyline.element.mouseover(function(){
			this.attr({"stroke-width": NORMAL_STROKE + 2});
		}).mouseout(function(){
			this.attr({"stroke-width": NORMAL_STROKE});
		});
		*/

    var last = polyline.getAnchorsCount() - 1;
    var x = polyline.getAnchor(last).x;
    var y = polyline.getAnchor(last).y;
    //var c = this.g.ellipse(x, y, 5, 5);

    var lastLineIndex = polyline.getLinesCount() - 1;
    var line = polyline.getLine(lastLineIndex);
    var firstLine = polyline.getLine(0);

    var arrowHead = null,
      circleTail = null,
      defaultSequenceFlowIndicator = null,
      conditionalSequenceFlowIndicator = null;

    if (connectionType == CONNECTION_TYPE.MESSAGE_FLOW) {
      circleTail = this._drawCircleTail(firstLine, connectionType);
    }
    if (withArrowHead) arrowHead = this._drawArrowHead(line, connectionType);

    //console.log("isDefault: ", isDefault, ", isDefaultConditionAvailable: ", polyline.isDefaultConditionAvailable);
    if (isDefault && polyline.isDefaultConditionAvailable) {
      //var angle = polyline.getLineAngle(0);
      //console.log("firstLine", firstLine);
      defaultSequenceFlowIndicator = this._drawDefaultSequenceFlowIndicator(firstLine);
    }

    if (conditional) {
      conditionalSequenceFlowIndicator = this._drawConditionalSequenceFlowIndicator(firstLine);
    }

    // draw flow name
    var flowName = contextObject.name;
    if (flowName) {
      var xPointArray = contextObject.xPointArray;
      var yPointArray = contextObject.yPointArray;
      var textX = xPointArray[0] < xPointArray[1] ? xPointArray[0] : xPointArray[1];
      var textY = yPointArray[0] < yPointArray[1] ? yPointArray[1] : yPointArray[0];
      // fix xy
      textX += 20;
      textY -= 10;
      this.g.text(textX, textY, flowName).attr(LABEL_FONT);
    }

    var st = this.g.set();
    st.push(polyline.element, arrowHead, circleTail, conditionalSequenceFlowIndicator);
    polyline.element.data('set', st);
    polyline.element.data('withArrowHead', withArrowHead);

    var polyCloneAttrNormal = { 'stroke-width': this.getStroke() + 5, stroke: Color.get(132, 112, 255), opacity: 0.0, cursor: 'hand' };
    var polyClone = st
      .clone()
      .attr(polyCloneAttrNormal)
      .hover(
        function () {
          //if (polyLine.data("isSelected")) return;
          polyClone.attr({ opacity: 0.2 });
        },
        function () {
          //if (polyLine.data("isSelected")) return;
          polyClone.attr({ opacity: 0.0 });
        }
      );
    polyClone.data('objectId', polyline.element.id);
    polyClone
      .click(function () {
        var instance = this;
        var objectId = instance.data('objectId');
        var object = this.paper.getById(objectId);
        var contextObject = object.data('contextObject');
        if (contextObject) {
          console.log('[flow], objectId: ' + object.id + ', flow: ' + contextObject.flow);
          ProcessDiagramGenerator.showFlowInfo(contextObject);
        }
      })
      .dblclick(function () {
        console.log('!!! DOUBLE CLICK !!!');
      })
      .hover(function (mouseEvent) {
        var instance = this;
        var objectId = instance.data('objectId');
        var object = this.paper.getById(objectId);
        var contextObject = object.data('contextObject');
        if (contextObject) ProcessDiagramGenerator.showFlowInfo(contextObject);
      });
    polyClone.data('parentId', uuid);

    if (!connectionType || connectionType == CONNECTION_TYPE.SEQUENCE_FLOW) polyline.element.attr('stroke-width', this.getStroke());
    else if (connectionType == CONNECTION_TYPE.MESSAGE_FLOW) polyline.element.attr({ 'stroke-dasharray': '--' });
    else if (connectionType == CONNECTION_TYPE.ASSOCIATION) polyline.element.attr({ 'stroke-dasharray': '. ' });

    this.setPaint(originalPaint);
    this.setStroke(originalStroke);
  },

  _drawDefaultSequenceFlowIndicator: function (line) {
    //console.log("line: ", line);

    var len = 10;
    (c = len / 2), (f = 8);
    var defaultIndicator = this.g.path('M' + -c + ' ' + 0 + 'L' + c + ' ' + 0);
    defaultIndicator.attr('stroke-width', this.getStroke() + 0);
    defaultIndicator.attr('stroke', this.getPaint());

    var cosAngle = Math.cos(line.angle);
    var sinAngle = Math.sin(line.angle);

    var dx = f * cosAngle;
    var dy = f * sinAngle;

    var x1 = line.x1 + dx + 0 * c * cosAngle;
    var y1 = line.y1 + dy + 0 * c * sinAngle;

    defaultIndicator.transform('t' + x1 + ',' + y1 + '');
    defaultIndicator.transform('...r' + Raphael.deg(line.angle - (3 * Math.PI) / 4) + ' ' + 0 + ' ' + 0);
    /*
		var c0 = this.g.ellipse(0, 0, 1, 1).attr({stroke: Color.Blue});
		c0.transform("t" + (line.x1) + "," + (line.y1) + "");
		var center = this.g.ellipse(0, 0, 1, 1).attr({stroke: Color.Red});
		center.transform("t" + (line.x1+dx) + "," + (line.y1+dy) + "");
		*/

    return defaultIndicator;
  },

  drawSequenceflow: function (waypoints, conditional, isDefault, highLighted) {
    var withArrowHead = true;
    this._drawFlow(waypoints, conditional, isDefault, highLighted, withArrowHead, CONNECTION_TYPE.SEQUENCE_FLOW);
  },

  drawMessageflow: function (waypoints, highLighted) {
    var withArrowHead = true;
    var conditional = (isDefault = false);
    this._drawFlow(waypoints, conditional, isDefault, highLighted, withArrowHead, CONNECTION_TYPE.MESSAGE_FLOW);
  },

  drawAssociation: function (waypoints, withArrowHead, highLighted) {
    var withArrowHead = withArrowHead;
    var conditional = (isDefault = false);
    this._drawFlow(waypoints, conditional, isDefault, highLighted, withArrowHead, CONNECTION_TYPE.ASSOCIATION);
  },

  _drawCircleTail: function (line, connectionType) {
    var diameter = (ARROW_WIDTH / 2) * 1.5;

    // anti smoothing
    if (this.strokeWidth % 2 == 1) (line.x1 += 0.5), (line.y1 += 0.5);

    var circleTail = this.g.ellipse(line.x1, line.y1, diameter, diameter);
    circleTail.attr('fill', Color.white);
    circleTail.attr('stroke', this.getPaint());

    return circleTail;
  },

  _drawArrowHead: function (line, connectionType) {
    var doubleArrowWidth = 2 * ARROW_WIDTH;

    if (connectionType == CONNECTION_TYPE.ASSOCIATION)
      var arrowHead = this.g.path(
        'M-' + (ARROW_WIDTH / 2 + 0.5) + ' -' + doubleArrowWidth + 'L 0 0 L' + (ARROW_WIDTH / 2 + 0.5) + ' -' + doubleArrowWidth
      );
    else
      var arrowHead = this.g.path(
        'M0 0L-' + (ARROW_WIDTH / 2 + 0.5) + ' -' + doubleArrowWidth + 'L' + (ARROW_WIDTH / 2 + 0.5) + ' -' + doubleArrowWidth + 'z'
      );

    //arrowHead.transform("t" + 0 + ",-" + this.getStroke() + "");

    // anti smoothing
    if (this.strokeWidth % 2 == 1) (line.x2 += 0.5), (line.y2 += 0.5);

    arrowHead.transform('t' + line.x2 + ',' + line.y2 + '');
    arrowHead.transform('...r' + Raphael.deg(line.angle - Math.PI / 2) + ' ' + 0 + ' ' + 0);

    if (!connectionType || connectionType == CONNECTION_TYPE.SEQUENCE_FLOW) arrowHead.attr('fill', this.getPaint());
    else if (connectionType == CONNECTION_TYPE.MESSAGE_FLOW) arrowHead.attr('fill', Color.white);

    arrowHead.attr('stroke-width', this.getStroke());
    arrowHead.attr('stroke', this.getPaint());

    return arrowHead;
  },

  /*
	drawArrowHead2: function(srcX, srcY, targetX, targetY) {
		var doubleArrowWidth = 2 * ARROW_WIDTH;
		
		//var arrowHead = this.g.path("M-" + ARROW_WIDTH/2 + " -" + doubleArrowWidth + "L0 0" + "L" + ARROW_WIDTH/2 + " -" + doubleArrowWidth + "z");
		
		var arrowHead = this.g.path("M0 0L-" + ARROW_WIDTH/1.5 + " -" + doubleArrowWidth + "L" + ARROW_WIDTH/1.5 + " -" + doubleArrowWidth + "z");
		//var c = DefaultProcessDiagramCanvas.g.ellipse(0, 0, 3, 3);
		//c.transform("t"+targetX+","+targetY+"");
		
		var angle = Math.atan2(targetY - srcY, targetX - srcX);
		
		arrowHead.transform("t"+targetX+","+targetY+"");
		arrowHead.transform("...r" + Raphael.deg(angle - Math.PI / 2) + " "+0+" "+0);
		
		//console.log(arrowHead.transform());
		//console.log("--> " + Raphael.deg(angle - Math.PI / 2));
		
		arrowHead.attr("fill", this.getPaint());
		arrowHead.attr("stroke", this.getPaint());
		
		/ *
		// shaddow
		var c0 = arrowHead.clone();
		c0.transform("...t-1 1");
		c0.attr("stroke-width", this.strokeWidth);
		c0.attr("stroke", Color.black);
		c0.attr("opacity", 0.15);
		c0.toBack();
		* /
	},
	*/

  _drawConditionalSequenceFlowIndicator: function (line) {
    var horizontal = CONDITIONAL_INDICATOR_WIDTH * 0.7;
    var halfOfHorizontal = horizontal / 2;
    var halfOfVertical = CONDITIONAL_INDICATOR_WIDTH / 2;

    var uuid = null;
    var waypoints = [
      { x: 0, y: 0 },
      { x: -halfOfHorizontal, y: halfOfVertical },
      { x: 0, y: CONDITIONAL_INDICATOR_WIDTH },
      { x: halfOfHorizontal, y: halfOfVertical },
    ];
    /*
		var polyline = new Polyline(uuid, waypoints, this.getStroke());
		polyline.element = this.g.path(polyline.path);
		polyline.element.attr("stroke-width", this.getStroke());
		polyline.element.attr("stroke", this.getPaint());
		polyline.element.id = uuid;
		*/
    var polygone = new Polygone(waypoints, this.getStroke());
    polygone.element = this.g.path(polygone.path);
    polygone.element.attr('fill', Color.white);

    polygone.transform('t' + line.x1 + ',' + line.y1 + '');
    polygone.transform('...r' + Raphael.deg(line.angle - Math.PI / 2) + ' ' + 0 + ' ' + 0);

    var cosAngle = Math.cos(line.angle);
    var sinAngle = Math.sin(line.angle);

    //polygone.element.attr("stroke-width", this.getStroke());
    //polygone.element.attr("stroke", this.getPaint());

    polygone.attr({ 'stroke-width': this.getStroke(), stroke: this.getPaint() });

    return polygone.element;
  },

  drawSequenceflowWithoutArrow: function (waypoints, conditional, isDefault, highLighted) {
    var withArrowHead = false;
    this._drawFlow(waypoints, conditional, isDefault, highLighted, withArrowHead, CONNECTION_TYPE.SEQUENCE_FLOW);
  },

  /*
   * Draw artifacts
   */

  drawPoolOrLane: function (x, y, width, height, name) {
    // anti smoothing
    if (this.strokeWidth % 2 == 1) (x = Math.round(x) + 0.5), (y = Math.round(y) + 0.5);

    // shape
    var rect = this.g.rect(x, y, width, height);
    var attr = { 'stroke-width': NORMAL_STROKE, stroke: TASK_STROKE_COLOR };
    rect.attr(attr);

    // Add the name as text, vertical
    if (name != null && name.length > 0) {
      var attr = POOL_LANE_FONT;

      // Include some padding
      var availableTextSpace = height - 6;

      // Create rotation for derived font
      var truncated = this.fitTextToWidth(name, availableTextSpace);
      var realWidth = this.getStringWidth(truncated, attr);
      var realHeight = this.getStringHeight(truncated, attr);

      //console.log("truncated:", truncated, ", height:", height, ", realHeight:", realHeight, ", availableTextSpace:", availableTextSpace, ", realWidth:", realWidth);
      var newX = x + 2 + realHeight * 1 - realHeight / 2;
      var newY = 3 + y + availableTextSpace - (availableTextSpace - realWidth) / 2 - realWidth / 2;
      var textElement = this.g.text(newX, newY, truncated).attr(attr);
      //console.log(".getBBox(): ", t.getBBox());
      textElement.transform('r' + Raphael.deg((270 * Math.PI) / 180) + ' ' + newX + ' ' + newY);
    }

    // TODO: add to set
  },

  _drawTask: function (name, x, y, width, height, thickBorder) {
    var originalPaint = this.getPaint();
    this.setPaint(TASK_COLOR);

    // anti smoothing
    if (this.strokeWidth % 2 == 1) (x = Math.round(x) + 0.5), (y = Math.round(y) + 0.5);

    // shape
    var shape = this.g.rect(x, y, width, height, TASK_CORNER_ROUND);
    var attr = { 'stroke-width': this.strokeWidth, stroke: TASK_STROKE_COLOR, fill: this.getPaint() };
    shape.attr(attr);
    //shape.attr({fill: "90-"+this.getPaint()+"-" + Color.get(250, 250, 244)});

    var contextObject = this.getConextObject();
    if (contextObject) {
      shape.id = contextObject.id;
      shape.data('contextObject', contextObject);
    }

    //var activity = this.getConextObject();
    //console.log("activity: " + activity.getId(), activity);
    //Object.clone(activity);

    /*
		c.mouseover(function(){
			this.attr({"stroke-width": NORMAL_STROKE + 2});
		}).mouseout(function(){
			this.attr({"stroke-width": NORMAL_STROKE});
		});
		*/

    this.setPaint(originalPaint);

    // white shaddow
    this.drawShaddow(shape);

    if (thickBorder) {
      shape.attr({ 'stroke-width': THICK_TASK_BORDER_STROKE });
    } else {
      //g.draw(rect);
    }

    // text
    if (name) {
      var fontAttr = TASK_FONT;

      // Include some padding
      var paddingX = 5;
      var paddingY = 5;
      var availableTextSpace = width - paddingX * 2;

      // TODO: this.setFont
      // var originalFont = this.getFont();
      // this.setFont(TASK_FONT)
      /*
			var truncated = this.fitTextToWidth(name, availableTextSpace);
			var realWidth = this.getStringWidth(truncated, fontAttr);
			var realHeight = this.getStringHeight(truncated, fontAttr);
			
			//var t = this.g.text(x + width/2 + realWidth*0/2 + paddingX*0, y + height/2, truncated).attr(fontAttr);
			*/
      //console.log("draw task name: " + name);
      var boxWidth = width - 2 * TEXT_PADDING;
      var boxHeight = height - ICON_SIZE - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2;
      var boxX = x + width / 2 - boxWidth / 2;
      var boxY = y + height / 2 - boxHeight / 2 + ICON_PADDING + ICON_PADDING - 2 - 2;
      /*
			var boxWidth = width - (2 * ANNOTATION_TEXT_PADDING);
			var boxHeight = height - (2 * ANNOTATION_TEXT_PADDING);
			var boxX = x + width/2 - boxWidth/2;
			var boxY = y + height/2 - boxHeight/2;
			*/

      this.drawTaskLabel(name, boxX, boxY, boxWidth, boxHeight);
    }
  },

  drawTaskLabel: function (text, x, y, boxWidth, boxHeight) {
    var originalFont = this.getFont();
    this.setFont(TASK_FONT);

    this._drawMultilineText(text, x, y, boxWidth, boxHeight, MULTILINE_VERTICAL_ALIGN_MIDDLE, MULTILINE_HORIZONTAL_ALIGN_MIDDLE);

    this.setFont(originalFont);
  },

  drawAnnotationText: function (text, x, y, width, height) {
    //this._drawMultilineText(text, x, y, width, height, "start");

    var originalPaint = this.getPaint();
    var originalFont = this.getFont();

    this.setPaint(Color.black);
    this.setFont(TASK_FONT);

    this._drawMultilineText(text, x, y, width, height, MULTILINE_VERTICAL_ALIGN_TOP, MULTILINE_HORIZONTAL_ALIGN_LEFT);

    this.setPaint(originalPaint);
    this.setFont(originalFont);
  },

  drawLabel: function (text, x, y, width, height) {
    //this._drawMultilineText(text, x, y, width, height, "start");

    var originalPaint = this.getPaint();
    var originalFont = this.getFont();

    this.setPaint(LABEL_COLOR);
    //this.setFont(LABEL_FONT);
    this.setFont(LABEL_FONT_SMOOTH);

    // predefined box width for labels
    // TODO: use label width as is, but not height (for stretching)
    if (!width || !height) {
      width = 100;
      height = 0;
    }

    // TODO: remove it. It is debug
    x = x - width / 2;

    this._drawMultilineText(text, x, y, width, height, MULTILINE_VERTICAL_ALIGN_TOP, MULTILINE_HORIZONTAL_ALIGN_MIDDLE);

    this.setPaint(originalPaint);
    this.setFont(originalFont);
  },

  /*
	drawMultilineLabel: function(text, x, y){
		var originalFont = this.getFont();
		this.setFont(LABEL_FONT_SMOOTH);
		
		var boxWidth = 80;
		x = x - boxWidth/2
		
		this._drawMultilineText(text, x, y, boxWidth, null, "middle");
		this.setFont(originalFont);
	},
	*/

  getStringWidth: function (text, fontAttrs) {
    var textElement = this.g.text(0, 0, text).attr(fontAttrs).hide();
    var bb = textElement.getBBox();

    //console.log("string width: ", t.getBBox().width);
    return textElement.getBBox().width;
  },
  getStringHeight: function (text, fontAttrs) {
    var textElement = this.g.text(0, 0, text).attr(fontAttrs).hide();
    var bb = textElement.getBBox();

    //console.log("string height: ", t.getBBox().height);
    return textElement.getBBox().height;
  },
  fitTextToWidth: function (original, width) {
    var text = original;

    // TODO: move attr on parameters
    var attr = { font: '11px Arial', opacity: 0 };

    // remove length for "..."
    var dots = this.g.text(0, 0, '...').attr(attr).hide();
    var dotsBB = dots.getBBox();

    var maxWidth = width - dotsBB.width;

    var textElement = this.g.text(0, 0, text).attr(attr).hide();
    var bb = textElement.getBBox();

    // it's a little bit incorrect with "..."
    while (bb.width > maxWidth && text.length > 0) {
      text = text.substring(0, text.length - 1);
      textElement.attr({ text: text });
      bb = textElement.getBBox();
    }

    // remove element from paper
    textElement.remove();

    if (text != original) {
      text = text + '...';
    }

    return text;
  },
  wrapTextToWidth: function (original, width) {
    //return original;

    var text = original;
    var wrappedText = '\n';

    // TODO: move attr on parameters
    var attr = { font: '11px Arial', opacity: 0 };

    var textElement = this.g.text(0, 0, wrappedText).attr(attr).hide();
    var bb = textElement.getBBox();

    var resultText = '';
    var i = 0,
      j = 0;
    while (text.length > 0) {
      while (bb.width < width && text.length > 0) {
        // remove "\n"
        wrappedText = wrappedText.substring(0, wrappedText.length - 1);
        // add new char, add "\n"
        wrappedText = wrappedText + text.substring(0, 1) + '\n';
        text = text.substring(1);

        textElement.attr({ text: wrappedText });
        bb = textElement.getBBox();
        i++;
        if (i > 200) break;
      }
      // remove "\n"
      wrappedText = wrappedText.substring(0, wrappedText.length - 1);

      if (text.length == 0) {
        resultText += wrappedText;
        break;
      }

      // return last char to text
      text = wrappedText.substring(wrappedText.length - 1) + text;
      // remove last char from wrappedText
      wrappedText = wrappedText.substring(0, wrappedText.length - 1) + '\n';

      textElement.attr({ text: wrappedText });
      bb = textElement.getBBox();

      //console.log(">> ", wrappedText, ", ", text);
      resultText += wrappedText;
      wrappedText = '\n';

      j++;
      if (j > 20) break;
    }
    // remove element from paper
    textElement.remove();

    return resultText;
  },

  wrapTextToWidth2: function (original, width) {
    var text = original;
    var wrappedText = '\n';

    // TODO: move attr on parameters
    var attr = { font: '11px Arial', opacity: 0 };

    var textElement = this.g.text(0, 0, wrappedText).attr(attr).hide();
    var bb = textElement.getBBox();

    var resultText = '';
    var i = 0,
      j = 0;
    while (text.length > 0) {
      while (bb.width < width && text.length > 0) {
        // remove "\n"
        wrappedText = wrappedText.substring(0, wrappedText.length - 1);
        // add new char, add "\n"
        wrappedText = wrappedText + text.substring(0, 1) + '\n';
        text = text.substring(1);

        textElement.attr({ text: wrappedText });
        bb = textElement.getBBox();
        i++;
        if (i > 200) break;
      }
      // remove "\n"
      wrappedText = wrappedText.substring(0, wrappedText.length - 1);

      if (text.length == 0) {
        resultText += wrappedText;
        break;
      }

      // return last char to text
      text = wrappedText.substring(wrappedText.length - 1) + text;
      // remove last char from wrappedText
      wrappedText = wrappedText.substring(0, wrappedText.length - 1) + '\n';

      textElement.attr({ text: wrappedText });
      bb = textElement.getBBox();

      //console.log(">> ", wrappedText, ", ", text);
      resultText += wrappedText;
      wrappedText = '\n';

      j++;
      if (j > 20) break;
    }
    // remove element from paper
    textElement.remove();

    return resultText;
  },

  drawUserTask: function (name, x, y, width, height) {
    this.g.setStart();
    this._drawTask(name, x, y, width, height);
    var img = this.g.image(USERTASK_IMAGE, x + ICON_PADDING, y + ICON_PADDING, ICON_SIZE, ICON_SIZE);
    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'task');
  },

  drawScriptTask: function (name, x, y, width, height) {
    this.g.setStart();
    this._drawTask(name, x, y, width, height);
    var img = this.g.image(SCRIPTTASK_IMAGE, x + ICON_PADDING, y + ICON_PADDING, ICON_SIZE, ICON_SIZE);
    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'task');
  },

  drawServiceTask: function (name, x, y, width, height) {
    this.g.setStart();
    this._drawTask(name, x, y, width, height);
    var img = this.g.image(SERVICETASK_IMAGE, x + ICON_PADDING, y + ICON_PADDING, ICON_SIZE, ICON_SIZE);
    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'task');
  },

  drawReceiveTask: function (name, x, y, width, height) {
    this.g.setStart();
    this._drawTask(name, x, y, width, height);
    var img = this.g.image(RECEIVETASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE);
    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'task');
  },

  drawSendTask: function (name, x, y, width, height) {
    this.g.setStart();
    this._drawTask(name, x, y, width, height);
    var img = this.g.image(SENDTASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE);
    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'task');
  },

  drawManualTask: function (name, x, y, width, height) {
    this.g.setStart();
    this._drawTask(name, x, y, width, height);
    var img = this.g.image(MANUALTASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE);
    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'task');
  },

  drawBusinessRuleTask: function (name, x, y, width, height) {
    this.g.setStart();
    this._drawTask(name, x, y, width, height);
    var img = this.g.image(BUSINESS_RULE_TASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE);
    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'task');
  },

  drawExpandedSubProcess: function (name, x, y, width, height, isTriggeredByEvent) {
    this.g.setStart();
    // anti smoothing
    if (this.strokeWidth % 2 == 1) (x = Math.round(x) + 0.5), (y = Math.round(y) + 0.5);

    // shape
    var rect = this.g.rect(x, y, width, height, EXPANDED_SUBPROCESS_CORNER_ROUND);

    // Use different stroke (dashed)
    if (isTriggeredByEvent) {
      rect.attr(EVENT_SUBPROCESS_ATTRS);
    } else {
      rect.attr(EXPANDED_SUBPROCESS_ATTRS);
    }

    this.setContextToElement(rect);

    var fontAttr = EXPANDED_SUBPROCESS_FONT;

    // Include some padding
    var paddingX = 10;
    var paddingY = 5;
    var availableTextSpace = width - paddingX * 2;

    var truncated = this.fitTextToWidth(name, availableTextSpace);
    var realWidth = this.getStringWidth(truncated, fontAttr);
    var realHeight = this.getStringHeight(truncated, fontAttr);

    var textElement = this.g
      .text(x + width / 2 - (realWidth * 0) / 2 + 0 * paddingX, y + realHeight / 2 + paddingY, truncated)
      .attr(fontAttr);

    var set = this.g.setFinish();
    // TODO: Expanded Sub Process may has specific handlers
    //this.addHandlers(set, x, y, width, height, "task");
  },

  drawCollapsedSubProcess: function (name, x, y, width, height, isTriggeredByEvent) {
    this.g.setStart();
    this._drawCollapsedTask(name, x, y, width, height, false);
    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'task');
  },

  drawCollapsedCallActivity: function (name, x, y, width, height) {
    this.g.setStart();
    this._drawCollapsedTask(name, x, y, width, height, true);
    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'task');
  },

  _drawCollapsedTask: function (name, x, y, width, height, thickBorder) {
    // The collapsed marker is now visualized separately
    this._drawTask(name, x, y, width, height, thickBorder);
  },

  drawCollapsedMarker: function (x, y, width, height) {
    // rectangle
    var rectangleWidth = MARKER_WIDTH;
    var rectangleHeight = MARKER_WIDTH;

    // anti smoothing
    if (this.strokeWidth % 2 == 1) y += 0.5;

    var rect = this.g.rect(x + (width - rectangleWidth) / 2, y + height - rectangleHeight - 3, rectangleWidth, rectangleHeight);

    // plus inside rectangle
    var cx = rect.attr('x') + rect.attr('width') / 2;
    var cy = rect.attr('y') + rect.attr('height') / 2;

    var line = this.g
      .path('M' + cx + ' ' + (cy + 2) + 'L' + cx + ' ' + (cy - 2) + 'M' + (cx - 2) + ' ' + cy + 'L' + (cx + 2) + ' ' + cy)
      .attr({ 'stroke-width': this.strokeWidth });
  },

  drawActivityMarkers: function (x, y, width, height, multiInstanceSequential, multiInstanceParallel, collapsed) {
    if (collapsed) {
      if (!multiInstanceSequential && !multiInstanceParallel) {
        this.drawCollapsedMarker(x, y, width, height);
      } else {
        this.drawCollapsedMarker(x - MARKER_WIDTH / 2 - 2, y, width, height);
        if (multiInstanceSequential) {
          console.log('is collapsed and multiInstanceSequential');
          this.drawMultiInstanceMarker(true, x + MARKER_WIDTH / 2 + 2, y, width, height);
        } else if (multiInstanceParallel) {
          console.log('is collapsed and multiInstanceParallel');
          this.drawMultiInstanceMarker(false, x + MARKER_WIDTH / 2 + 2, y, width, height);
        }
      }
    } else {
      if (multiInstanceSequential) {
        console.log('is multiInstanceSequential');
        this.drawMultiInstanceMarker(true, x, y, width, height);
      } else if (multiInstanceParallel) {
        console.log('is multiInstanceParallel');
        this.drawMultiInstanceMarker(false, x, y, width, height);
      }
    }
  },

  drawGateway: function (x, y, width, height) {
    var rhombus = this.g.path(
      'M' +
        x +
        ' ' +
        (y + height / 2) +
        'L' +
        (x + width / 2) +
        ' ' +
        (y + height) +
        'L' +
        (x + width) +
        ' ' +
        (y + height / 2) +
        'L' +
        (x + width / 2) +
        ' ' +
        y +
        'z'
    );

    // white shaddow
    this.drawShaddow(rhombus);

    rhombus.attr('stroke-width', this.strokeWidth);
    rhombus.attr('stroke', Color.SlateGrey);
    rhombus.attr({ fill: Color.white });

    this.setContextToElement(rhombus);

    return rhombus;
  },

  drawParallelGateway: function (x, y, width, height) {
    this.g.setStart();

    // rhombus
    this.drawGateway(x, y, width, height);

    // plus inside rhombus
    var originalStroke = this.getStroke();
    this.setStroke(GATEWAY_TYPE_STROKE);

    var plus = this.g.path(
      'M' +
        (x + 10) +
        ' ' +
        (y + height / 2) +
        'L' +
        (x + width - 10) +
        ' ' +
        (y + height / 2) + // horizontal
        'M' +
        (x + width / 2) +
        ' ' +
        (y + height - 10) +
        'L' +
        (x + width / 2) +
        ' ' +
        (y + 10) // vertical
    );
    plus.attr({ 'stroke-width': this.getStroke(), stroke: this.getPaint() });

    this.setStroke(originalStroke);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'gateway');
  },

  drawExclusiveGateway: function (x, y, width, height) {
    this.g.setStart();

    // rhombus
    var rhombus = this.drawGateway(x, y, width, height);

    var quarterWidth = width / 4;
    var quarterHeight = height / 4;

    // X inside rhombus
    var originalStroke = this.getStroke();
    this.setStroke(GATEWAY_TYPE_STROKE);

    var iks = this.g.path(
      'M' +
        (x + quarterWidth + 3) +
        ' ' +
        (y + quarterHeight + 3) +
        'L' +
        (x + 3 * quarterWidth - 3) +
        ' ' +
        (y + 3 * quarterHeight - 3) +
        'M' +
        (x + quarterWidth + 3) +
        ' ' +
        (y + 3 * quarterHeight - 3) +
        'L' +
        (x + 3 * quarterWidth - 3) +
        ' ' +
        (y + quarterHeight + 3)
    );
    iks.attr({ 'stroke-width': this.getStroke(), stroke: this.getPaint() });

    this.setStroke(originalStroke);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'gateway');
  },

  drawInclusiveGateway: function (x, y, width, height) {
    this.g.setStart();

    // rhombus
    this.drawGateway(x, y, width, height);

    var diameter = width / 4;

    // circle inside rhombus
    var originalStroke = this.getStroke();
    this.setStroke(GATEWAY_TYPE_STROKE);
    var circle = this.g.ellipse(width / 2 + x, height / 2 + y, diameter, diameter);
    circle.attr({ 'stroke-width': this.getStroke(), stroke: this.getPaint() });

    this.setStroke(originalStroke);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'gateway');
  },

  drawEventBasedGateway: function (x, y, width, height) {
    this.g.setStart();

    // rhombus
    this.drawGateway(x, y, width, height);

    var diameter = width / 2;

    // rombus inside rhombus
    var originalStroke = this.getStroke();
    this.setStroke(GATEWAY_TYPE_STROKE);

    // draw GeneralPath (polygon)
    var n = 5;
    var angle = (2 * Math.PI) / n;
    var x1Points = [];
    var y1Points = [];

    for (var index = 0; index < n; index++) {
      var v = index * angle - Math.PI / 2;
      x1Points[index] = x + parseInt(Math.round(width / 2)) + parseInt(Math.round((width / 4) * Math.cos(v)));
      y1Points[index] = y + parseInt(Math.round(height / 2)) + parseInt(Math.round((height / 4) * Math.sin(v)));
    }
    //g.drawPolygon(x1Points, y1Points, n);

    var path = '';
    for (var index = 0; index < n; index++) {
      if (index == 0) path += 'M';
      else path += 'L';
      path += x1Points[index] + ',' + y1Points[index];
    }
    path += 'z';
    var polygone = this.g.path(path);
    polygone.attr('stroke-width', this.strokeWidth);
    polygone.attr('stroke', this.getPaint());

    this.setStroke(originalStroke);

    var set = this.g.setFinish();
    this.addHandlers(set, x, y, width, height, 'gateway');
  },

  /*
   *  drawMultiInstanceMarker
   *  drawHighLight
   *  highLightFlow
   */

  drawMultiInstanceMarker: function (sequential, x, y, width, height) {
    var rectangleWidth = MARKER_WIDTH;
    var rectangleHeight = MARKER_WIDTH;

    // anti smoothing
    if (this.strokeWidth % 2 == 1) x += 0.5; //, y += .5;

    var lineX = x + (width - rectangleWidth) / 2;
    var lineY = y + height - rectangleHeight - 3;

    var originalStroke = this.getStroke();
    this.setStroke(MULTI_INSTANCE_STROKE);

    if (sequential) {
      var line = this.g
        .path(
          'M' +
            lineX +
            ' ' +
            lineY +
            'L' +
            (lineX + rectangleWidth) +
            ' ' +
            lineY +
            'M' +
            lineX +
            ' ' +
            (lineY + rectangleHeight / 2) +
            'L' +
            (lineX + rectangleWidth) +
            ' ' +
            (lineY + rectangleHeight / 2) +
            'M' +
            lineX +
            ' ' +
            (lineY + rectangleHeight) +
            'L' +
            (lineX + rectangleWidth) +
            ' ' +
            (lineY + rectangleHeight)
        )
        .attr({ 'stroke-width': this.strokeWidth });
    } else {
      var line = this.g
        .path(
          'M' +
            lineX +
            ' ' +
            lineY +
            'L' +
            lineX +
            ' ' +
            (lineY + rectangleHeight) +
            'M' +
            (lineX + rectangleWidth / 2) +
            ' ' +
            lineY +
            'L' +
            (lineX + rectangleWidth / 2) +
            ' ' +
            (lineY + rectangleHeight) +
            'M' +
            (lineX + rectangleWidth) +
            ' ' +
            lineY +
            'L' +
            (lineX + rectangleWidth) +
            ' ' +
            (lineY + rectangleHeight)
        )
        .attr({ 'stroke-width': this.strokeWidth });
    }

    this.setStroke(originalStroke);
  },

  drawHighLight: function (x, y, width, height) {
    var originalPaint = this.getPaint();
    var originalStroke = this.getStroke();

    this.setPaint(HIGHLIGHT_COLOR);
    this.setStroke(THICK_TASK_BORDER_STROKE);

    //var c = this.g.rect(x - width/2 - THICK_TASK_BORDER_STROKE, y - height/2 - THICK_TASK_BORDER_STROKE, width + THICK_TASK_BORDER_STROKE*2, height + THICK_TASK_BORDER_STROKE*2, 5);
    var rect = this.g.rect(
      x - THICK_TASK_BORDER_STROKE,
      y - THICK_TASK_BORDER_STROKE,
      width + THICK_TASK_BORDER_STROKE * 2,
      height + THICK_TASK_BORDER_STROKE * 2,
      TASK_CORNER_ROUND
    );
    rect.attr('stroke-width', this.strokeWidth);
    rect.attr('stroke', this.getPaint());

    this.setPaint(originalPaint);
    this.setStroke(originalStroke);
  },

  highLightActivity: function (activityId) {
    var shape = this.g.getById(activityId);
    if (!shape) {
      console.error('Activity ' + activityId + ' not found');
      return;
    }

    var contextObject = shape.data('contextObject');
    if (contextObject)
      console.log('--> highLightActivity: [' + contextObject.getProperty('type') + '], activityId: ' + contextObject.getId());
    else console.log('--> highLightActivity: ', shape, shape.data('contextObject'));

    shape.attr('stroke-width', THICK_TASK_BORDER_STROKE);
    shape.attr('stroke', HIGHLIGHT_COLOR);
  },

  highLightFlow: function (flowId) {
    var shapeFlow = this.g.getById(flowId);
    if (!shapeFlow) {
      console.error('Flow ' + flowId + ' not found');
      return;
    }

    var contextObject = shapeFlow.data('contextObject');
    if (contextObject) console.log('--> highLightFlow: [' + contextObject.id + '] ' + contextObject.flow);
    //console.log("--> highLightFlow: ", flow.flow, flow.data("set"));

    var st = shapeFlow.data('set');

    st.attr('stroke-width', SEQUENCEFLOW_HIGHLIGHT_STROKE);
    st.attr('stroke', HIGHLIGHT_COLOR);
    var withArrowHead = shapeFlow.data('withArrowHead');
    if (withArrowHead) st[1].attr('fill', HIGHLIGHT_COLOR);

    st.forEach(function (el) {
      //console.log("---->", el);
      //el.attr("")
    });
  },

  _drawClock: function (cx, cy, width, height) {
    var circle = this.g.ellipse(cx, cy, 1, 1).attr({ stroke: 'none', fill: Color.get(232, 239, 241) });
    //var c = this.g.ellipse(cx, cy, width, height).attr({stroke:"none", fill: Color.red});
    //x = cx - width/2;
    //y = cy - height/2;

    var clock = this.g.path(
      /* outer circle */ 'M15.5,2.374		C8.251,2.375,2.376,8.251,2.374,15.5		C2.376,22.748,8.251,28.623,15.5,28.627c7.249-0.004,13.124-5.879,13.125-13.127C28.624,8.251,22.749,2.375,15.5,2.374z' +
        /* inner circle */ 'M15.5,26.623	C8.909,26.615,4.385,22.09,4.375,15.5	C4.385,8.909,8.909,4.384,15.5,4.374c4.59,0.01,11.115,3.535,11.124,11.125C26.615,22.09,22.091,26.615,15.5,26.623z' +
        /*  9 */ 'M8.625,15.5c-0.001-0.552-0.448-0.999-1.001-1c-0.553,0-1,0.448-1,1c0,0.553,0.449,1,1,1C8.176,16.5,8.624,16.053,8.625,15.5z' +
        /*  8 */ 'M8.179,18.572c-0.478,0.277-0.642,0.889-0.365,1.367c0.275,0.479,0.889,0.641,1.365,0.365c0.479-0.275,0.643-0.887,0.367-1.367C9.27,18.461,8.658,18.297,8.179,18.572z' +
        /* 10 */ 'M9.18,10.696c-0.479-0.276-1.09-0.112-1.366,0.366s-0.111,1.09,0.365,1.366c0.479,0.276,1.09,0.113,1.367-0.366C9.821,11.584,9.657,10.973,9.18,10.696z' +
        /*  2 */ 'M22.822,12.428c0.478-0.275,0.643-0.888,0.366-1.366c-0.275-0.478-0.89-0.642-1.366-0.366c-0.479,0.278-0.642,0.89-0.366,1.367C21.732,12.54,22.344,12.705,22.822,12.428z' +
        /*  7 */ 'M12.062,21.455c-0.478-0.275-1.089-0.111-1.366,0.367c-0.275,0.479-0.111,1.09,0.366,1.365c0.478,0.277,1.091,0.111,1.365-0.365C12.704,22.344,12.54,21.732,12.062,21.455z' +
        /* 11 */ 'M12.062,9.545c0.479-0.276,0.642-0.888,0.366-1.366c-0.276-0.478-0.888-0.642-1.366-0.366s-0.642,0.888-0.366,1.366C10.973,9.658,11.584,9.822,12.062,9.545z' +
        /*  4 */ 'M22.823,18.572c-0.48-0.275-1.092-0.111-1.367,0.365c-0.275,0.479-0.112,1.092,0.367,1.367c0.477,0.275,1.089,0.113,1.365-0.365C23.464,19.461,23.3,18.848,22.823,18.572z' +
        /*  2 */ 'M19.938,7.813c-0.477-0.276-1.091-0.111-1.365,0.366c-0.275,0.48-0.111,1.091,0.366,1.367s1.089,0.112,1.366-0.366C20.581,8.702,20.418,8.089,19.938,7.813z' +
        /*  3 */ 'M23.378,14.5c-0.554,0.002-1.001,0.45-1.001,1c0.001,0.552,0.448,1,1.001,1c0.551,0,1-0.447,1-1C24.378,14.949,23.929,14.5,23.378,14.5z' +
        /* arrows */ 'M15.501,6.624c-0.552,0-1,0.448-1,1l-0.466,7.343l-3.004,1.96c-0.478,0.277-0.642,0.889-0.365,1.365c0.275,0.479,0.889,0.643,1.365,0.367l3.305-1.676C15.39,16.99,15.444,17,15.501,17c0.828,0,1.5-0.671,1.5-1.5l-0.5-7.876C16.501,7.072,16.053,6.624,15.501,6.624z' +
        /*  9 */ 'M15.501,22.377c-0.552,0-1,0.447-1,1s0.448,1,1,1s1-0.447,1-1S16.053,22.377,15.501,22.377z' +
        /*  8 */ 'M18.939,21.455c-0.479,0.277-0.643,0.889-0.366,1.367c0.275,0.477,0.888,0.643,1.366,0.365c0.478-0.275,0.642-0.889,0.366-1.365C20.028,21.344,19.417,21.18,18.939,21.455z' +
        ''
    );
    clock.attr({ fill: Color.black, stroke: 'none' });
    //clock.transform("t " + (cx-29.75/2) + " " + (cy-29.75/2));
    //clock.transform("...s 0.85");

    //clock.transform("...s " + .85 + " " + .85);
    clock.transform('t ' + -2.374 + ' ' + -2.374);
    clock.transform('...t -' + (15.5 - 2.374) + ' -' + (15.5 - 2.374));
    clock.transform('...s ' + 1 * (width / 35) + ' ' + 1 * (height / 35));
    clock.transform('...T ' + cx + ' ' + cy);
    //clock.transform("t " + (cx-width/2) + " " + (cy-height/2));

    //console.log(".getBBox(): ", clock.getBBox());
    //console.log(".attr(): ", c.attrs);
    circle.attr('rx', clock.getBBox().width / 2);
    circle.attr('ry', clock.getBBox().height / 2);

    //return circle
  },

  _drawPentagon: function (cx, cy, width, height, filled) {
    // draw GeneralPath (polygon)
    var n = 5;
    var angle = (2 * Math.PI) / n;
    var waypoints = [];

    for (var index = 0; index < n; index++) {
      var v = index * angle - Math.PI / 2;
      var point = {};
      point.x = (-width * 1.2) / 2 + parseInt(Math.round((width * 1.2) / 2)) + parseInt(Math.round(((width * 1.2) / 4) * Math.cos(v)));
      point.y = (-height * 1.2) / 2 + parseInt(Math.round((height * 1.2) / 2)) + parseInt(Math.round(((height * 1.2) / 4) * Math.sin(v)));
      waypoints[index] = point;
    }

    var polygone = new Polygone(waypoints, this.getStroke());
    polygone.element = this.g.path(polygone.path);
    if (filled) polygone.element.attr('fill', Color.black);
    else polygone.element.attr('fill', Color.white);

    polygone.element.transform('s ' + 1 * (width / 35) + ' ' + 1 * (height / 35));
    polygone.element.transform('...T ' + cx + ' ' + cy);
  },

  //_drawMultilineText: function(text, x, y, boxWidth, boxHeight, textAnchor) {
  _drawMultilineText: function (text, x, y, boxWidth, boxHeight, verticalAlign, horizontalAlign) {
    if (!text || text == '') return;

    // Autostretch boxHeight if boxHeight is 0
    if (boxHeight == 0) verticalAlign = MULTILINE_VERTICAL_ALIGN_TOP;

    //var TEXT_PADDING = 3;
    var width = boxWidth;
    if (boxHeight) var height = boxHeight;

    var layouts = [];

    //var font = {font: "11px Arial", opacity: 1, "fill": LABEL_COLOR};
    var font = this.getFont();
    var measurer = new LineBreakMeasurer(this.g, x, y, text, font);
    var lineHeight = measurer.rafaelTextObject.getBBox().height;
    //console.log("text: ", text.replace(/\n/g, "?"));

    if (height) {
      var availableLinesCount = parseInt(height / lineHeight);
      //console.log("availableLinesCount: " + availableLinesCount);
    }

    var i = 1;
    while (measurer.getPosition() < measurer.text.getEndIndex()) {
      var layout = measurer.nextLayout(width);
      //console.log("LAYOUT: " + layout + ", getPosition: " + measurer.getPosition());

      if (layout != null) {
        // TODO: and check if measurer has next layout. If no then don't draw  dots
        if (!availableLinesCount || i < availableLinesCount) {
          layouts.push(layout);
        } else {
          layouts.push(this.fitTextToWidth(layout + '...', boxWidth));
          break;
        }
      }
      i++;
    }
    //console.log(layouts);

    measurer.rafaelTextObject.attr({ text: layouts.join('\n') });

    if (horizontalAlign) measurer.rafaelTextObject.attr({ 'text-anchor': horizontalAlign }); // end, middle, start

    var bb = measurer.rafaelTextObject.getBBox();
    // TODO: there is somethin wrong with wertical align. May be: measurer.rafaelTextObject.attr({"y": y + height/2 - bb.height/2})
    measurer.rafaelTextObject.attr({ y: y + bb.height / 2 });
    //var bb = measurer.rafaelTextObject.getBBox();

    if (measurer.rafaelTextObject.attr('text-anchor') == MULTILINE_HORIZONTAL_ALIGN_MIDDLE)
      measurer.rafaelTextObject.attr('x', x + boxWidth / 2);
    else if (measurer.rafaelTextObject.attr('text-anchor') == MULTILINE_HORIZONTAL_ALIGN_RIGHT)
      measurer.rafaelTextObject.attr('x', x + boxWidth);

    var boxStyle = { stroke: Color.LightSteelBlue2, 'stroke-width': 1.0, 'stroke-dasharray': '- ' };
    //var box = this.g.rect(x+.5, y + .5, width, height).attr(boxStyle);
    var textAreaCX = x + boxWidth / 2;
    var height = boxHeight;
    if (!height) height = bb.height;
    var textAreaCY = y + height / 2;
    var dotLeftTop = this.g.ellipse(x, y, 3, 3).attr({ 'stroke-width': 0, fill: Color.LightSteelBlue, stroke: 'none' }).hide();
    var dotCenter = this.g.ellipse(textAreaCX, textAreaCY, 3, 3).attr({ fill: Color.LightSteelBlue2, stroke: 'none' }).hide();

    /*
				// real bbox
				var bb = measurer.rafaelTextObject.getBBox();
				var rect = paper.rect(bb.x+.5, bb.y + .5, bb.width, bb.height).attr({"stroke-width": 1});
				*/
    var rect = this.g.rect(x, y, boxWidth, height).attr({ 'stroke-width': 1 }).attr(boxStyle).hide();
    var debugSet = this.g.set();
    debugSet.push(dotLeftTop, dotCenter, rect);
    //debugSet.show();
  },

  drawTextAnnotation: function (text, x, y, width, height) {
    var lineLength = 18;
    var path = [];
    path.push(['M', x + lineLength, y]);
    path.push(['L', x, y]);
    path.push(['L', x, y + height]);
    path.push(['L', x + lineLength, y + height]);

    path.push(['L', x + lineLength, y + height - 1]);
    path.push(['L', x + 1, y + height - 1]);
    path.push(['L', x + 1, y + 1]);
    path.push(['L', x + lineLength, y + 1]);
    path.push(['z']);

    var textAreaLines = this.g.path(path);

    var boxWidth = width - 2 * ANNOTATION_TEXT_PADDING;
    var boxHeight = height - 2 * ANNOTATION_TEXT_PADDING;
    var boxX = x + width / 2 - boxWidth / 2;
    var boxY = y + height / 2 - boxHeight / 2;

    // for debug
    var rectStyle = { stroke: Color(112, 146, 190), 'stroke-width': 1.0, 'stroke-dasharray': '- ' };
    var r = this.g.rect(boxX, boxY, boxWidth, boxHeight).attr(rectStyle);
    //

    this.drawAnnotationText(text, boxX, boxY, boxWidth, boxHeight);
  },

  drawLabel111111111: function (text, x, y, width, height, labelAttrs) {
    var debug = false;

    // text
    if (text != null && text != undefined && text != '') {
      var attr = LABEL_FONT;

      //console.log("x", x, "y", y, "width", width, "height", height );

      wrappedText = text;
      if (labelAttrs && labelAttrs.wrapWidth) {
        wrappedText = this.wrapTextToWidth(wrappedText, labelAttrs.wrapWidth);
      }
      var realWidth = this.getStringWidth(wrappedText, attr);
      var realHeight = this.getStringHeight(wrappedText, attr);

      var textAreaCX = x + width / 2;
      var textAreaCY = y + 3 + height + this.getStringHeight(wrappedText, attr) / 2;

      var textX = textAreaCX;
      var textY = textAreaCY;

      var textAttrs = {};
      if (labelAttrs && labelAttrs.align) {
        switch (labelAttrs.align) {
          case 'left':
            textAttrs['text-anchor'] = 'start';
            textX = textX - realWidth / 2;
            break;
          case 'center':
            textAttrs['text-anchor'] = 'middle';
            break;
          case 'right':
            textAttrs['text-anchor'] = 'end';
            textX = textX + realWidth / 2;
            break;
        }
      }
      if (labelAttrs && labelAttrs.wrapWidth) {
        if (true) {
          // Draw frameborder
          var textAreaStyle = { stroke: Color.LightSteelBlue2, 'stroke-width': 1.0, 'stroke-dasharray': '- ' };
          var textAreaX = textAreaCX - realWidth / 2;
          var textAreaY = textAreaCY + 0.5 - realHeight / 2;
          var textArea = this.g.rect(textAreaX, textAreaY, realWidth, realHeight).attr(textAreaStyle);

          var textAreaLines = this.g.path(
            'M' +
              textAreaX +
              ' ' +
              textAreaY +
              'L' +
              (textAreaX + realWidth) +
              ' ' +
              (textAreaY + realHeight) +
              'M' +
              +(textAreaX + realWidth) +
              ' ' +
              textAreaY +
              'L' +
              textAreaX +
              ' ' +
              (textAreaY + realHeight)
          );
          textAreaLines.attr(textAreaStyle);

          this.g.ellipse(textAreaCX, textAreaCY, 3, 3).attr({ fill: Color.LightSteelBlue2, stroke: 'none' });
        }
      }

      var label = this.g.text(textX, textY, wrappedText).attr(attr).attr(textAttrs);
      //label.id = Raphael.createUUID();
      //console.log("label ", label.id, ", ", wrappedText);

      if (this.fontSmoothing) {
        label.attr({ stroke: LABEL_COLOR, 'stroke-width': 0.4 });
      }

      // debug
      if (debug) {
        var imageAreaStyle = { stroke: Color.grey61, 'stroke-width': 1.0, 'stroke-dasharray': '- ' };
        var imageArea = this.g.rect(x + 0.5, y + 0.5, width, height).attr(imageAreaStyle);
        var imageAreaLines = this.g.path(
          'M' + x + ' ' + y + 'L' + (x + width) + ' ' + (y + height) + 'M' + +(x + width) + ' ' + y + 'L' + x + ' ' + (y + height)
        );
        imageAreaLines.attr(imageAreaStyle);
        var dotStyle = { fill: Color.Coral, stroke: 'none' };
        this.g.ellipse(x, y, 3, 3).attr(dotStyle);
        this.g.ellipse(x + width, y, 2, 2).attr(dotStyle);
        this.g.ellipse(x + width, y + height, 2, 2).attr(dotStyle);
        this.g.ellipse(x, y + height, 2, 2).attr(dotStyle);
      }

      return label;
    }
  },

  vvoid: function () {},
};
